/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ArtificialAgent;

import BayesNetwork.*;
import java.awt.Point;
import java.util.ArrayList;
import wumpusworld.Main;
import wumpusworld.MyCanvas;
import wumpusworld.WumpusEngine;
/**
 *
 * @author Jedrek
 */
public class ArtificialAgent {

    private BayesNetwork bayesNetwork = null;
    WumpusEngine wumpusEngine = null;

    public float wumpusImportance = 100f;
    public float pitImportance = 100f;
    public float moveCost = 2f;
    float winCost = 10000f;

    private final Object lock;

    public int winCount = 0;
    public int lostCount = 0;

    public ArtificialAgent(){
        lock = new Object();
    }

    public ArtificialAgent(WumpusEngine wumpusEngine){
        lock = new Object();
        this.wumpusEngine = wumpusEngine;
    }

    public void setWumpusEngine(WumpusEngine wumpusEngine){
        this.wumpusEngine = wumpusEngine;
    }

    /**
     * Creates a Bayes Network structure
     * @param radius for 1 only neighbouring cells will be taken into consideration,
     * for two the closest cells and these who are 2 cells far, etc.
     */
    public void createNet(int radius){
        synchronized(lock){
            bayesNetwork = new BayesNetwork(radius);
        }
    }
    
    public void teach(ArrayList<ArrayList<String>> teachingSet){
        if(bayesNetwork == null)
            return;
        
        System.out.println("Teaching...");
        
        for(int i = 0; i<teachingSet.get(0).size(); i++){
            synchronized(lock){
                bayesNetwork.teachAllNodes(teachingSet.get(0).get(i));
            }
        }
        for(int i = 0; i<teachingSet.get(1).size(); i++){
            synchronized(lock){
                bayesNetwork.teachAllNodes(teachingSet.get(1).get(i));
            }
        }
        
        System.out.println("Teaching finished.");
        
//        Main.structureWindow.repaintStructure();
        
//        System.out.println("Wumpus: " + bayesNetwork.getTrueProbability("wumpus", "sulfur-10=T,sulfur10=T,sulfur0-1=T,sulfur01=T"));
//        System.out.println("Wumpus: " + bayesNetwork.getTrueProbability("wumpus", "sulfur-10=T,sulfur10=T,sulfur0-1=F"));
//        System.out.println("Wumpus: " + bayesNetwork.getTrueProbability("wumpus", "sulfur-10=T,sulfur10=T"));
//        System.out.println("Wumpus: " + bayesNetwork.getTrueProbability("wumpus", "sulfur-10=T"));
//
//        System.out.println("Pit: " + bayesNetwork.getTrueProbability("pit", "pit=T"));
//        System.out.println("Wumpus: " + bayesNetwork.getTrueProbability("wumpus", "wumpus=T"));
    }

    public float getTrueProbability(int c, int r, String queryNode){
        if(wumpusEngine == null){
            System.err.println("'WumpusEngine' not set! Can't perform 'getTrueProbability()'.");
            return 0;
        }
//        if(!wumpusEngine.isInitialized()){
//            System.err.println("'WumpusEngine' is not initialized! Can't perform 'getTrueProbability()'.");
//            return 0;
//        }
        synchronized(lock){
            return bayesNetwork.getTrueProbability(queryNode, getEventStateForCell(c, r));
        }
    }

    ArrayList<String> getEventStateForCell(int c, int r){
        ArrayList<String> event = new ArrayList<String>();

        if(wumpusEngine == null || !wumpusEngine.isInitialized())
            return event;

        for(int ci=-bayesNetwork.getRadius(); ci<=bayesNetwork.getRadius(); ci++){
            for(int ri=-bayesNetwork.getRadius(); ri<=bayesNetwork.getRadius(); ri++){
                if(ri == 0 && ci == 0)
                    continue;
                
                int cell = wumpusEngine.getCellState(c+ci, r+ri);

                if(cell == -1)
                    continue;
                
                if((cell & MyCanvas.HIDDEN) != 0){
                    //we do nothing if cell is uncovered
                } else{
                    if((cell & MyCanvas.WIND) != 0){
                        event.add("wind" + (ci) + (ri) + "=T");
                    } else{
                        event.add("wind" + (ci) + (ri) + "=F");
                    }
                    if((cell & MyCanvas.SULFUR) != 0){
                        event.add("sulfur" + (ci) + (ri) + "=T");
                    } else{
                        event.add("sulfur" + (ci) + (ri) + "=F");
                    }
                }
            }
        }

        //test
//        System.out.println("Dla komórki: "+c+", "+r);
//        for(int i = 0; i<event.size(); i++)
//            System.out.println(event.get(i));
//        System.out.println();

        return event;
    }

    public float [][] createMarkovDecisionMatrix(){
        float [][] markovMap;
        int [][] map;

        ArrayList<Point> goalCells;

        if(wumpusEngine == null){
            System.err.println("'WumpusEngine' not set! Can't perform 'createMarkovDecisionMatrix()'.");
            return null;
        }
        if(!wumpusEngine.isInitialized()){
            System.err.println("'WumpusEngine' is not initialized! Can't perform 'createMarkovDecisionMatrix()'.");
            return null;
        }

        //retriving map size
        Point mapSize = wumpusEngine.getCurrentMapSize();
        if(mapSize.x <= 0 || mapSize.y <= 0)
            return null;

        //retriving current map state
        map = new int [mapSize.x][mapSize.y];
        for(int c = 0; c<map.length; c++)
            for(int r = 0; r<map[c].length; r++)
                map[c][r] = wumpusEngine.getCellState(c, r);

        //initializing markovMap
        markovMap = new float [mapSize.x][mapSize.y];
        for(int c = 0; c<markovMap.length; c++)
            for(int r = 0; r<markovMap[c].length; r++)
                markovMap[c][r] = - winCost;

        goalCells = new ArrayList<Point>();
        //setting values of possibly final cells (hidden or enterence to the cave)
        for(int c = 0; c<markovMap.length; c++)
            for(int r = 0; r<markovMap[c].length; r++){
                //is this an enterence
                if( (map[c][r] & (MyCanvas.ENTERENCE_DOWN | MyCanvas.ENTERENCE_LEFT | MyCanvas.ENTERENCE_RIGHT | MyCanvas.ENTERENCE_UP)) != 0 )
                {
                    //does the agent have gold (then it wins)
                    if(wumpusEngine.isGoldFound()){
                        markovMap[c][r] = winCost;
                        goalCells.add(new Point(c, r));
                    }
                }
                //is it hidden cell which is adjacent to non-hidden cell (can be uncovered)
                if( (map[c][r] & MyCanvas.HIDDEN) != 0 ){
                    if( c>0 && (map[c-1][r] & MyCanvas.HIDDEN)==0 ||
                            c<map.length-1 && (map[c+1][r] & MyCanvas.HIDDEN)==0 ||
                            r>0 && (map[c][r-1] & MyCanvas.HIDDEN)==0 ||
                            r<map[c].length-1 && (map[c][r+1] & MyCanvas.HIDDEN)==0 )
                    {
                        goalCells.add(new Point(c, r));
                        
                        float probabilityOfPit = getTrueProbability(c, r, "pit");
                        float probabilityOfWumpus = getTrueProbability(c, r, "wumpus");

                        markovMap[c][r] = -pitImportance*probabilityOfPit -wumpusImportance*probabilityOfWumpus;
                    }
                }
            }

        //filling rest of cells in markov matrix
        while(goalCells.size()>0){
            int c = goalCells.get(0).x;
            int r = goalCells.get(0).y;
            goalCells.remove(0);

            if( c>0 && (map[c-1][r] & MyCanvas.HIDDEN)==0 ){
                if( markovMap[c-1][r]<markovMap[c][r]-moveCost ){
                    markovMap[c-1][r] = markovMap[c][r]-moveCost;
                    goalCells.add(new Point(c-1, r));
                }
            }
            if( c<map.length-1 && (map[c+1][r] & MyCanvas.HIDDEN)==0 ){
                if( markovMap[c+1][r]<markovMap[c][r]-moveCost ){
                    markovMap[c+1][r] = markovMap[c][r]-moveCost;
                    goalCells.add(new Point(c+1, r));
                }
            }
            if( r>0 && (map[c][r-1] & MyCanvas.HIDDEN)==0 ){
                if( markovMap[c][r-1]<markovMap[c][r]-moveCost ){
                    markovMap[c][r-1] = markovMap[c][r]-moveCost;
                    goalCells.add(new Point(c, r-1));
                }
            }
            if( r<map[c].length-1 && (map[c][r+1] & MyCanvas.HIDDEN)==0 ){
                if( markovMap[c][r+1]<markovMap[c][r]-moveCost ){
                    markovMap[c][r+1] = markovMap[c][r]-moveCost;
                    goalCells.add(new Point(c, r+1));
                }
            }
        }

        //test
//        System.out.println("Markov matrix:");
//        for(int r = 0; r<markovMap[0].length; r++){
//            System.out.println();
//            for(int c = 0; c<markovMap.length; c++)
//                System.out.printf("%7.1f, ", markovMap[c][r]);
//        }
//        System.out.println();

        return markovMap;
    }

    public int getPreferredDirection(int x, int y){
        if(wumpusEngine == null)
            return -1;
        if(!wumpusEngine.isInitialized())
            return -1;
        
        float [][] markovMatrix = createMarkovDecisionMatrix();

        float leftGradient = x>0 ? markovMatrix[x-1][y]-markovMatrix[x][y] : 0;
        float rightGradient = x<wumpusEngine.getCurrentMapSize().x-1 ? markovMatrix[x+1][y]-markovMatrix[x][y] : 0;
        float upGradient = y>0 ? markovMatrix[x][y-1]-markovMatrix[x][y] : 0;
        float downGradient = y<wumpusEngine.getCurrentMapSize().x-1 ? markovMatrix[x][y+1]-markovMatrix[x][y] : 0;

        float best = leftGradient;
        int direction = WumpusEngine.DIRECTION_LEFT;
        if(rightGradient>best){
            best = rightGradient;
            direction = WumpusEngine.DIRECTION_RIGHT;
        }
        if(upGradient>best){
            best = upGradient;
            direction = WumpusEngine.DIRECTION_UP;
        }
        if(downGradient>best){
            best = downGradient;
            direction = WumpusEngine.DIRECTION_DOWN;
        }

        return direction;
    }


    public int getPreferredDirection(){
        return getPreferredDirection(wumpusEngine.getAgentPosition().x, wumpusEngine.getAgentPosition().y);
    }

    public void learnFromCurrentPosition(){
        int x;
        int y;
        int radius;
        Point mapSize;
        ArrayList<String> event;

        if(wumpusEngine == null)
            return;
        if(!wumpusEngine.isInitialized())
            return;

        synchronized(lock){
            x = wumpusEngine.getAgentPosition().x;
            y = wumpusEngine.getAgentPosition().y;
            mapSize = wumpusEngine.getCurrentMapSize();

            radius = bayesNetwork.getRadius();
            event = new ArrayList<String>();

            if( (wumpusEngine.getCellState(x, y) & MyCanvas.PIT) != 0)
                event.add("pit=T");
            else
                event.add("pit=F");

            if( (wumpusEngine.getCellState(x, y) & MyCanvas.WUMPUS) != 0)
                event.add("wumpus=T");
            else
                event.add("wumpus=F");

            for(int c = -radius; c<=radius; c++)
                for(int r = -radius; r<=radius; r++){
                    if(r == 0 && c == 0)
                        continue;
                    if(c+x<0 || c+x>=mapSize.x || r+y<0 || r+y>=mapSize.y)
                        continue;

                    if( (wumpusEngine.getCellState(x+c, y+r) & MyCanvas.HIDDEN) != 0)
                        continue;

                    if( (wumpusEngine.getCellState(x+c, y+r) & MyCanvas.WIND) != 0)
                        event.add("wind"+(c)+(r)+"=T");
                    else
                        event.add("wind"+(c)+(r)+"=F");

                    if( (wumpusEngine.getCellState(x+c, y+r) & MyCanvas.SULFUR) != 0)
                        event.add("sulfur"+(c)+(r)+"=T");
                    else
                        event.add("sulfur"+(c)+(r)+"=F");
                }

    //        System.out.println();
    //        for(int i = 0; i<event.size(); i++)
    //            System.out.println(event.get(i));

            bayesNetwork.teachAllNodes(event);
        }
    }

    public void setMemorySize(int memorySize){
        synchronized(lock){
            bayesNetwork.setMemorySize(memorySize);
        }
    }
}
